package kit4j.ui.cs.swing.paging;

import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import kit4j.util.lang.ObjectUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 滚动翻页的信息分页展示面板。
 * 
 * @author MiniKnife
 * 
 * @param <T>
 *            要显示的数据的类型
 */
public abstract class RollPageablePanel<T> extends JPanel {

	private static final long serialVersionUID = 6341885800864424693L;

	/**
	 * 翻页方向——从北向南(从上到下)。
	 */
	public static final int PAGING_DIR_NOTTH_TO_SOUTH = 1;

	/**
	 * 翻页方向——从南向北(从下到上)。
	 */
	public static final int PAGING_DIR_SOUTH_TO_NORTH = -1;

	/**
	 * 翻页方向——从西向东(从左到右)。
	 */
	public static final int PAGING_DIR_EAST_TO_WEST = 2;

	/**
	 * 翻页方向——从东向西(从右到左)。
	 */
	public static final int PAGING_DIR_WEST_TO_EAST = -2;

	private static final Logger log = LoggerFactory.getLogger(RollPageablePanel.class);

	/**
	 * 默认翻页方向。
	 */
	private static int DEFAULT_PAGING_DIR;

	/**
	 * 默认翻页等待时间。
	 */
	private static int DEFAULT_PAGING_TIME_INTERVAL;

	/**
	 * 默认翻页时每次移动的像素。
	 */
	private static int DEFAULT_PAGING_MOVE_LOCATION_INTERVAL;

	/**
	 * 默认翻页时每次移动的时间间隔。
	 */
	private static int DEFAULT_PAGING_MOVE_TIME_INTERVAL;

	/**
	 * 每页大小(显示的数据数量)。
	 */
	private int pageSize;

	/**
	 * 翻页方向。
	 */
	private int pagingDir;

	/**
	 * 翻页等待时间。
	 */
	private long pagingTimeInterval;

	/**
	 * 翻页时每次移动的像素。
	 */
	private int moveLocationInterval;

	/**
	 * 翻页时每次移动的时间间隔。
	 */
	private long moveTimeInterval;

	/**
	 * 页面上边距。
	 */
	private int pageTopMargin;

	/**
	 * 页面左边距。
	 */
	private int pageLeftMargin;

	/**
	 * 页面下边距。
	 */
	private int pageBottomMargin;

	/**
	 * 页面右边距。
	 */
	private int pageRightMargin;

	/**
	 * 要展示的车辆维修数据列表。
	 */
	private List<T> dataList;

	private Object dataListLock;

	private boolean isResetDataList;

	private Thread showDataThread;

	protected JPanel showingPaneA;

	protected JPanel showingPaneB;

	private boolean isShowingPaneA;

	private List<T> pageList;

	private int showingDataFromIndex;

	private int showingDataToIndex;

	/**
	 * @param dataList
	 *            要显示的数据列表
	 * @param pageSize
	 *            每页大小
	 */
	public RollPageablePanel(List<T> dataList, int pageSize) {
		this(dataList, pageSize, DEFAULT_PAGING_DIR);
	}

	/**
	 * @param dataList
	 *            要显示的数据列表
	 * @param pageSize
	 *            每页大小
	 * @param pagingDir
	 *            翻页方向
	 */
	public RollPageablePanel(List<T> dataList, int pageSize, int pagingDir) {
		super(null);

		dataListLock = new Object();
		pageList = new ArrayList<T>(pageSize);
		showingPaneA = new JPanel(null);
		showingPaneB = new JPanel(null);
		setDataList(dataList);
		setPageSize(pageSize);
		setPagingDir(pagingDir);
		setPagingTimeInterval(DEFAULT_PAGING_TIME_INTERVAL);
		setMoveLocationInterval(DEFAULT_PAGING_MOVE_LOCATION_INTERVAL);
		setMoveTimeInterval(DEFAULT_PAGING_MOVE_TIME_INTERVAL);
		setPageTopMargin(0);
		setPageLeftMargin(0);
		setPageBottomMargin(0);
		setPageRightMargin(0);

		add(showingPaneA);
		add(showingPaneB);
		initShowDataThread();
	}

	private void initShowDataThread() {
		showDataThread = new Thread() {
			@Override
			public void run() {
				isResetDataList = false;
				isShowingPaneA = false;
				showingDataFromIndex = 0;
				showingDataToIndex = pageSize;

				while (true) {
					nextPage();

					try {
						TimeUnit.MILLISECONDS.sleep(pagingTimeInterval);
					} catch (InterruptedException e) {
						log.error("error in waiting paging", e);
					}
				}
			}
		};

		showDataThread.start();
	}

	/**
	 * 显示下一页数据。
	 * 
	 * @user : WuZheng
	 * @date : 2012-11-17
	 */
	public void nextPage() {
		JPanel topPane, bottomPane;

		if (isShowingPaneA) {
			topPane = showingPaneA;
			bottomPane = showingPaneB;
		} else {
			topPane = showingPaneB;
			bottomPane = showingPaneA;
		}

		initShowingPane(bottomPane, getNextPageDatas());
		moveShowingPane(topPane, bottomPane);

		isShowingPaneA = !isShowingPaneA;
		showingDataFromIndex = showingDataToIndex;
		showingDataToIndex += pageSize;
	}

	/**
	 * 获取下一页要显示的数据。
	 * 
	 * @return 下一页要显示的数据。
	 */
	protected List<T> getNextPageDatas() {
		pageList.clear();

		int dataSize = 0;
		synchronized (dataListLock) {
			if (dataList != null) {
				dataSize = dataList.size();
			}

			if (dataSize == 0) {
				return null;
			}

			if (showingDataFromIndex >= dataSize) {
				isResetDataList = true;
			}
			if (isResetDataList) {
				showingDataFromIndex = 0;
				showingDataToIndex = pageSize;
			}
			if (showingDataToIndex > dataSize) {
				if (pageSize < dataSize) {
					pageList.addAll(dataList.subList(showingDataFromIndex, dataSize));
					showingDataFromIndex = 0;
					showingDataToIndex = showingDataToIndex - dataSize;

					pageList.addAll(dataList.subList(showingDataFromIndex, showingDataToIndex));
				} else {
					showingDataToIndex = dataSize;
					pageList.addAll(dataList.subList(showingDataFromIndex, showingDataToIndex));
				}
			} else {
				pageList.addAll(dataList.subList(showingDataFromIndex, showingDataToIndex));
			}
		}

		log.debug("getNextPageDatas:showingDataFromIndex={},showingDataToIndex={},toShowingDataSize={},dataSize={}",
				new Object[] { showingDataFromIndex, showingDataToIndex, pageList.size(), dataSize });

		return pageList;
	}

	/**
	 * 根据给定的要显示的数据初始化显示面板。
	 * 
	 * @param showingPane
	 *            要显示数据的面板
	 * @param pageDataList
	 *            要显示的数据列表
	 */
	protected void initShowingPane(JPanel showingPane, List<T> pageDataList) {
		showingPane.removeAll();
		int pageDataSize = pageDataList != null ? pageDataList.size() : 0;

		log.debug("initShowingPane:showingPane={}, pageDataSize={}", showingPane, pageDataSize);

		if (pageDataSize > 0) {
			final int height = getHeight() - getPageTopMargin() - getPageBottomMargin();
			final int width = getWidth() - getPageLeftMargin() - getPageRightMargin();
			int rowHeight = height / pageSize;
			int dataHeight = 0;

			T data;
			for (int i = 0; i < pageDataSize; ++i) {
				data = pageDataList.get(i);

				if (data == null) {
					continue;
				}

				rowHeight = (height * (i + 1) / pageSize) - dataHeight;
				Component renderComp = getRenderComponent(data, i + 1);

				renderComp.setBounds(getPageLeftMargin(), dataHeight, width, rowHeight);
				showingPane.add(renderComp);

				dataHeight += rowHeight;
			}
		}
	}

	/**
	 * 获取给定对象的渲染组件。
	 * 
	 * @param value
	 *            要渲染的对象
	 * @param index
	 *            给定对象在当前页的顺序索引(从1开始)
	 * @return 给定对象的渲染组件。
	 */
	protected Component getRenderComponent(T value, int index) {
		return new JLabel(String.valueOf(value));
	}

	/**
	 * 对显示面板进行换页的移动操作。
	 * 
	 * @param showingPane
	 *            显示中的面板
	 * @param toShowingPane
	 *            待显示的面板
	 * @throws NullPointerException
	 *             showingPane或toShowingPane为null。
	 */
	protected void moveShowingPane(final JPanel showingPane, final JPanel toShowingPane) {
		final int width = getWidth();
		final int height = getHeight();
		showingPane.setSize(width, height);
		toShowingPane.setSize(width, height);

		switch (getPagingDir()) {
		case PAGING_DIR_SOUTH_TO_NORTH:
			horizontalMove(showingPane, toShowingPane, width, height, moveLocationInterval);
			break;
		case PAGING_DIR_NOTTH_TO_SOUTH:
			horizontalMove(showingPane, toShowingPane, width, -height, -moveLocationInterval);
			break;
		case PAGING_DIR_WEST_TO_EAST:
			verticalMove(showingPane, toShowingPane, width, height, moveLocationInterval);
			break;
		case PAGING_DIR_EAST_TO_WEST:
			verticalMove(showingPane, toShowingPane, -width, height, -moveLocationInterval);
			break;
		default:
			break;
		}
	}

	private void horizontalMove(final JPanel showingPane, final JPanel toShowingPane, final int width,
			final int height, final int moveInterval) {
		final int leftMargin = 0;
		int moveY = 0;

		while (true) {
			if ((moveInterval > 0 && moveY >= height) || (moveInterval < 0 && moveY <= height)) {
				break;
			} else {
				moveY += moveInterval;

				if ((moveInterval > 0 && moveY > height) || (moveInterval < 0 && moveY < height)) {
					moveY = height;
				}
			}

			final int tempMoveY = moveY;
			SwingUtilities.invokeLater(new Runnable() {

				@Override
				public void run() {
					showingPane.setLocation(leftMargin, -tempMoveY);
					toShowingPane.setLocation(leftMargin, height - tempMoveY);
				}
			});

			try {
				TimeUnit.MILLISECONDS.sleep(moveTimeInterval);
			} catch (InterruptedException e) {
				log.error("error in moveShowingPane", e);
			}
		}
	}

	private void verticalMove(final JPanel showingPane, final JPanel toShowingPane, final int width, final int height,
			final int moveInterval) {
		final int topMargin = 0;
		int moveX = 0;

		while (true) {
			if ((moveInterval > 0 && moveX >= width) || (moveInterval < 0 && moveX <= width)) {
				break;
			} else {
				moveX += moveInterval;

				if ((moveInterval > 0 && moveX > width) || (moveInterval < 0 && moveX < width)) {
					moveX = width;
				}
			}

			final int tempMoveX = moveX;
			SwingUtilities.invokeLater(new Runnable() {

				@Override
				public void run() {
					showingPane.setLocation(tempMoveX, topMargin);
					toShowingPane.setLocation(tempMoveX - width, topMargin);
				}
			});

			try {
				TimeUnit.MILLISECONDS.sleep(moveTimeInterval);
			} catch (InterruptedException e) {
				log.error("error in moveShowingPane", e);
			}
		}
	}

	/**
	 * @return the dataList
	 */
	public List<T> getDataList() {
		return new ArrayList<T>(dataList);
	}

	/**
	 * @param dataList
	 *            the dataList to set
	 */
	public void setDataList(List<T> dataList) {
		if (!ObjectUtil.equals(this.dataList, dataList)) {
			synchronized (dataListLock) {
				this.dataList = dataList;
				isResetDataList = true;
			}
		}
	}

	/**
	 * @return the pageSize
	 */
	public int getPageSize() {
		return pageSize;
	}

	/**
	 * @param pageSize
	 *            the pageSize to set
	 */
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	/**
	 * @return the pagingDir
	 */
	public int getPagingDir() {
		return pagingDir;
	}

	/**
	 * @param pagingDir
	 *            the pagingDir to set
	 */
	public void setPagingDir(int pagingDir) {
		this.pagingDir = pagingDir;
	}

	/**
	 * @return the pagingTimeInterval
	 */
	public long getPagingTimeInterval() {
		return pagingTimeInterval;
	}

	/**
	 * @param pagingTimeInterval
	 *            the pagingTimeInterval to set
	 */
	public void setPagingTimeInterval(long pagingTimeInterval) {
		this.pagingTimeInterval = pagingTimeInterval;
	}

	/**
	 * @return the moveLocationInterval
	 */
	public int getMoveLocationInterval() {
		return moveLocationInterval;
	}

	/**
	 * @param moveLocationInterval
	 *            the moveLocationInterval to set
	 */
	public void setMoveLocationInterval(int moveLocationInterval) {
		this.moveLocationInterval = moveLocationInterval;
	}

	/**
	 * @return the moveTimeInterval
	 */
	public long getMoveTimeInterval() {
		return moveTimeInterval;
	}

	/**
	 * @param moveTimeInterval
	 *            the moveTimeInterval to set
	 */
	public void setMoveTimeInterval(long moveTimeInterval) {
		this.moveTimeInterval = moveTimeInterval;
	}

	/**
	 * @see javax.swing.JComponent#setBackground(java.awt.Color)
	 */
	@Override
	public void setBackground(Color bg) {
		super.setBackground(bg);
		showingPaneA.setBackground(bg);
		showingPaneB.setBackground(bg);
	}

	/**
	 * @see javax.swing.JComponent#setForeground(java.awt.Color)
	 */
	@Override
	public void setForeground(Color fg) {
		super.setForeground(fg);
		showingPaneA.setForeground(fg);
		showingPaneB.setForeground(fg);
	}

	/**
	 * @return the pageTopMargin
	 */
	public int getPageTopMargin() {
		return pageTopMargin;
	}

	/**
	 * @param pageTopMargin
	 *            the pageTopMargin to set
	 */
	public void setPageTopMargin(int pageTopMargin) {
		this.pageTopMargin = pageTopMargin;
	}

	/**
	 * @return the pageLeftMargin
	 */
	public int getPageLeftMargin() {
		return pageLeftMargin;
	}

	/**
	 * @param pageLeftMargin
	 *            the pageLeftMargin to set
	 */
	public void setPageLeftMargin(int pageLeftMargin) {
		this.pageLeftMargin = pageLeftMargin;
	}

	/**
	 * @return the pageBottomMargin
	 */
	public int getPageBottomMargin() {
		return pageBottomMargin;
	}

	/**
	 * @param pageBottomMargin
	 *            the pageBottomMargin to set
	 */
	public void setPageBottomMargin(int pageBottomMargin) {
		this.pageBottomMargin = pageBottomMargin;
	}

	/**
	 * @return the pageRightMargin
	 */
	public int getPageRightMargin() {
		return pageRightMargin;
	}

	/**
	 * @param pageRightMargin
	 *            the pageRightMargin to set
	 */
	public void setPageRightMargin(int pageRightMargin) {
		this.pageRightMargin = pageRightMargin;
	}
}
